/**
 *  路由装饰器
 */

import Router from 'koa-router'
import 'reflect-metadata'
import { Context, Next } from 'koa'
import { isArray, pick } from 'lodash'

type MethodType = 'get' | 'post' | 'put' | 'patch' | 'delete'
let rootRouter: Router | null = null

// 要注入的 serivce 储存
const injectableWeakMap = new WeakMap()

const fixPath = (path: string) => {
  return path.startsWith('/') ? path : '/' + path
}
// 转换 通用
const reqConvert = (methodType: MethodType) => {
  return function (reqPath): MethodDecorator {
    reqPath = fixPath(reqPath)
    // targetClassProtoType 类的原型对象
    // methodName 方法名称
    // methodDesc 方法属性描述(可读的，冻结的等等)
    // 函数装饰器
    return (targetClassProtoType, methodName, methodDesc: PropertyDescriptor) => {
      // 把 path 和 请求类型 存起来 做标识
      Reflect.defineMetadata('path', reqPath, targetClassProtoType, methodName)
      Reflect.defineMetadata('methodType', methodType, targetClassProtoType, methodName)
    }
  }
}
const handleInjectServices = (targetClass: { new (...args); instance: any }) => {
  // 属性类型元数据 design:type ：用于获取类属性的类型
  // 参数类型元数据 design:paramtypes：用于获取方法参数的类型
  // 返回类型元数据 design:returntype：用于获取返回值的类型
  let injectServices = Reflect.getMetadata('design:paramtypes', targetClass) || []
  let serivceInstances = injectServices.map((serviceClass) => {
    const serivceInstance = injectableWeakMap.get(serviceClass)
    return (
      serivceInstance ||
      console.warn(`${serviceClass.name} 类需要使用 Injectable 装饰器，不然无法注入`)
    )
  })
  return targetClass.instance || (targetClass.instance = new targetClass(...serivceInstances))
}
// 类装饰器
export const Controller = (modulePath: string = '/'): ClassDecorator => {
  modulePath = fixPath(modulePath)
  return function (targetClass) {
    const instance = handleInjectServices(targetClass as any)
    // 新建一个模块router
    const moduleRouter = new Router()
    // 设置前置 路径
    moduleRouter.prefix(modulePath)
    // 看看有没有 模块 middlewares
    const moduleMiddlewares = Reflect.getMetadata('middlewares', targetClass.prototype) || []
    //注册模块中间件
    moduleRouter.use(...moduleMiddlewares)
    // 循环class里面写的函数，意思是循环原型里面的值
    Object.getOwnPropertyNames(targetClass.prototype).forEach((methodName) => {
      // 获取 路由 path 如果使用@Get这些装饰器就会有
      const path = Reflect.getMetadata('path', targetClass.prototype, methodName)
      if (!path) return
      // 函数本体
      // const routerHandleFn = targetClass.prototype[methodName].bind(instance)
      const routerHandleFn = (ctx) => handleParams(ctx, targetClass.prototype, methodName, instance)
      // 函数中间件
      const middlewares =
        Reflect.getMetadata('middlewares', targetClass.prototype, methodName) || []
      // router 请求类型
      const methodType: MethodType = Reflect.getMetadata(
        'methodType',
        targetClass.prototype,
        methodName
      )
      // 注册路由 router.get(path, ()=>{})
      moduleRouter[methodType](path, ...middlewares, routerHandleFn)
    })
    // 根路由注册模块路由
    rootRouter?.use(moduleRouter.routes(), moduleRouter.allowedMethods())
  }
}

/**
 *
 * @param args
 * @returns
 */
export function Middle(...args: ((ctx: Context, next: Next) => any)[]) {
  return (proOrClass, methodName?: string, methodDesc?: PropertyDescriptor) => {
    if (!methodName) {
      Reflect.defineMetadata('middlewares', args, proOrClass.prototype)
    } else {
      // 是 function
      Reflect.defineMetadata('middlewares', args, proOrClass, methodName)
    }
  }
}

export const Get = reqConvert('get')
export const Post = reqConvert('post')
export const Put = reqConvert('put')
export const Patch = reqConvert('patch')
export const Delete = reqConvert('delete')

// 储存跟路由 入口必须要先调用这个函数
export const initDecoratorRouter = (router: Router) => {
  rootRouter = router
}

// 依赖注入 装饰器
export const Injectable = (): ClassDecorator => {
  return (targetClass) => {
    if (!injectableWeakMap.get(targetClass)) {
      injectableWeakMap.set(targetClass, new (targetClass as any)())
    }
  }
}
function handleParams(ctx, prototype, methodName, instance) {
  const argsArr = Reflect.getMetadata('argNames', prototype[methodName]) || []
  const fn = prototype[methodName].bind(instance)
  const args: any = []
  argsArr.forEach((arg) => {
    let val = arg.ctxRoot ? ctx[arg.ctxKey] : ctx.request[arg.ctxKey]
    if (arg.key) {
      if (isArray(arg.key)) {
        val = pick(val, arg.key)
      } else {
        val = val[arg.key]
      }
    }
    args[arg.argIndex - 1] = val
  })
  return fn(ctx, ...args)
}
const paramsCovert = (ctxKey, ctxRoot = false) => {
  return (key: string | string[] = ''): ParameterDecorator => {
    return (prototype, methodName, argIndex) => {
      const argsArr = Reflect.getMetadata('argNames', prototype[methodName]) || []
      argsArr.push({
        argIndex,
        ctxKey,
        ctxRoot,
        key,
      })
      Reflect.defineMetadata('argNames', argsArr, prototype[methodName])
    }
  }
}
export const Body = paramsCovert('body')
export const Query = paramsCovert('query')
export const Params = paramsCovert('params', true)
